package com.yumo.kangchenjunga.coupon.v2;

import java.time.Instant;
import java.time.ZoneId;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.transaction.Transactional;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import com.yumo.kangchenjunga.activity.ActivityService;
import com.yumo.kangchenjunga.activity.NoSuchActivityException;
import com.yumo.kangchenjunga.company.CompanyService;
import com.yumo.kangchenjunga.company.CompanyVo;
import com.yumo.kangchenjunga.company.NoSuchCompanyException;
import com.yumo.kangchenjunga.company.NoSuchStoreException;
import com.yumo.kangchenjunga.company.StoreVo;
import com.yumo.kangchenjunga.coupon.v1.CouponCustomerAllocationRepository;
import com.yumo.kangchenjunga.coupon.v1.CouponCustomerObtainmentEntity;
import com.yumo.kangchenjunga.coupon.v1.CouponCustomerObtainmentRepository;
import com.yumo.kangchenjunga.coupon.v1.CouponLogEntity;
import com.yumo.kangchenjunga.coupon.v1.CouponLogService;
import com.yumo.kangchenjunga.coupon.v1.CouponStatus;
import com.yumo.kangchenjunga.coupon.v1.NoMoreAllocatedCouponException;
import com.yumo.kangchenjunga.coupon.v1.ObtainVo;
import com.yumo.kangchenjunga.coupon.v1.VerifyCouponVo;
import com.yumo.kangchenjunga.user.NoSuchUserException;
import com.yumo.kangchenjunga.user.UserService;

import cn.leafcraft.utils.webutil.datetime.DateTimeUtils;

@Service("couponServiceV2")
public class CouponService {

	@Autowired
	private CouponCustomerAllocationRepository allocationRepository;
	@Autowired
	private CouponCustomerObtainmentRepository obtainmentRepository;

	@Autowired
	private UserService userService;
	@Autowired
	private ActivityService activityService;
	@Autowired
	private CompanyService companyService;
	@Autowired
	private CouponLogService couponLogService;
	@Autowired
	private JdbcTemplate jdbcTemplate;

	public static final String OP_VERIFY = "verify";
	public static final String OP_REDEEM = "redeem";
	public static final String RESULT_SUCCESS = "success";
	public static final String RESULT_FAILURE = "failure";

	@Transactional
	public ObtainVo obtain(int customerId, int activityId)
			throws NoMoreAllocatedCouponException, NoSuchActivityException {
		var coupon = allocationRepository.findFirstByCustomerIdAndActivityIdAndStatusOrderById(customerId, activityId,
				CouponStatus.ALLOCATED);
		var activity = activityService.findById(activityId);

		if (coupon.isPresent()) {
			var allocation = coupon.get();
			CouponCustomerObtainmentEntity obtain = new CouponCustomerObtainmentEntity();
			var now = DateTimeUtils.instantNow();

			allocation.status = CouponStatus.OBTAINED;
			obtain.activity = allocation.activity;
			obtain.couponCode = allocation.couponCode;
			obtain.couponId = allocation.couponId;
			obtain.customer = allocation.customer;
			obtain.obtainTime = DateTimeUtils.instantNow();

			obtain.effectiveTime = now.atZone(ZoneId.systemDefault()).toLocalDate();

			if (obtain.effectiveTime.isBefore(activity.startDate)) {
				obtain.effectiveTime = activity.startDate;
			}

			obtain.expirationTime = obtain.effectiveTime.plusDays(allocation.termByMinute / 1440);

			// endDate 还是指结束那天而不是结束那天的次日，所以要加1。
			if (obtain.expirationTime.isAfter(activity.endDate.plusDays(1))) {
				obtain.expirationTime = activity.endDate.plusDays(1);
			}

			if (obtain.effectiveTime.isAfter(obtain.expirationTime)) {
				obtain.effectiveTime = obtain.expirationTime;
			}

			obtain.secretCode = allocation.secretCode;
			obtain.status = CouponStatus.OBTAINED;

			allocationRepository.save(allocation);
			obtainmentRepository.save(obtain);

			return new ObtainVo(customerId, activityId, obtain.couponCode, obtain.secretCode, obtain.effectiveTime,
					obtain.expirationTime);
		}

		throw new NoMoreAllocatedCouponException();
	}

	public VerifyCouponVo verify(int activityId, String couponCode, String secretCode, int opStoreId, int opUserId)
			throws NoSuchUserException, NoSuchStoreException, NoSuchCompanyException {
		var now = DateTimeUtils.instantNow();
		var user = userService.queryByIdAndStoreId(opUserId, opStoreId);
		var store = companyService.queryStoreById(opStoreId);
		var company = companyService.queryById(store.companyId);
		Optional<CouponCustomerObtainmentEntity> entity;

		if (StringUtils.trimToNull(secretCode) != null) {
			entity = obtainmentRepository.findByActivityIdAndCouponCodeAndSecretCode(activityId, couponCode,
					secretCode);
		} else {
			entity = obtainmentRepository.findByActivityIdAndCouponCode(activityId, couponCode);
		}

		return entity.map(e -> doVerify(e, couponCode, secretCode, company, store, now, opUserId, user.username))
				.orElseGet(() -> invalidCoupon(couponCode, secretCode, company, store, now, opUserId, user.username,
						"无效优惠券"));
	}

	private VerifyCouponVo doVerify(CouponCustomerObtainmentEntity coupon, String couponCode, String secretCode,
			CompanyVo company, StoreVo store, Instant opTime, int opUserId, String opUsername) {
		var nowLocalDate = opTime.atZone(ZoneId.systemDefault()).toLocalDate();

		if (0 != Integer.compare(company.id, coupon.activity.company.getId())) {
			return invalidCoupon(couponCode, secretCode, company, store, opTime, opUserId, opUsername, "无效优惠券");
		}

		if (coupon.status == CouponStatus.USED) {
			return invalidCoupon(couponCode, secretCode, company, store, opTime, opUserId, opUsername, "优惠券已使用过");
		}

		if (coupon.status == CouponStatus.OBTAINED) {
			if (nowLocalDate.isBefore(coupon.effectiveTime)) {
				return invalidCoupon(couponCode, secretCode, company, store, opTime, opUserId, opUsername, "优惠券未到生效日期");
			}

			if (!nowLocalDate.isBefore(coupon.expirationTime)) {
				return invalidCoupon(couponCode, secretCode, company, store, opTime, opUserId, opUsername, "优惠券已过期");
			}

			if (!allowStore(coupon.activity.getId(), store.id)) {
				return invalidCoupon(couponCode, secretCode, company, store, opTime, opUserId, opUsername,
						"该门店不在参与范围内");
			}

			return validCoupon(coupon, couponCode, secretCode, company, store, opTime, opUserId, opUsername);
		}

		return invalidCoupon(couponCode, secretCode, company, store, opTime, opUserId, opUsername, "无效优惠券");
	}

	private boolean allowStore(int activityId, int storeId) {
		var sql = String.format("select count(1) from activity_store_map where activity_id = %d and store_id = %d",
				activityId, storeId);
		var result = jdbcTemplate.queryForList(sql, Integer.class);

		return result.get(0) > 0;
	}

	private VerifyCouponVo validCoupon(CouponCustomerObtainmentEntity coupon, String couponCode, String secretCode,
			CompanyVo company, StoreVo store, Instant opTime, int opUserId, String opUsername) {
		var verifyLog = new CouponLogEntity();

		verifyLog.activityId = coupon.activity.getId();
		verifyLog.activityTitle = coupon.activity.title;
		verifyLog.companyId = company.id;
		verifyLog.companyName = company.name;
		verifyLog.couponId = coupon.getId();
		verifyLog.customerId = coupon.customer.getId();
		verifyLog.customerNumber = coupon.customer.customerNumber;
		verifyLog.inputCouponCode = couponCode;
		verifyLog.inputSecretCode = secretCode;
		verifyLog.op = OP_VERIFY;
		verifyLog.opResult = RESULT_SUCCESS;
		verifyLog.opTime = opTime;
		verifyLog.opUserId = opUserId;
		verifyLog.opUsername = opUsername;
		verifyLog.remark = null;
		verifyLog.storeId = store.companyId;
		verifyLog.storeName = store.name;

		var list = obtainmentRepository.findByActivityAndCustomerAndStatusAndCouponCodeNot(coupon.activity,
				coupon.customer, CouponStatus.OBTAINED, coupon.couponCode);
		var others = list.stream()
				.map(it -> CouponLogEntity.builder()
						.activity(it.activity.getId(), it.activity.title)
						.coupon(it.getId())
						.customer(it.customer.getId(), it.customer.customerNumber)
						.input(it.couponCode, it.secretCode)
						.op(OP_VERIFY, opUserId, opUsername, opTime, RESULT_SUCCESS, null, null)
						.store(company.id, company.name, store.id, store.name)
						.build())
				.collect(Collectors.toList());

		couponLogService.saveLog(verifyLog);
		couponLogService.saveLogs(others);

		return new VerifyCouponVo(verifyLog, others);
	}

	private VerifyCouponVo invalidCoupon(String couponCode, String secretCode, CompanyVo company, StoreVo store,
			Instant opTime, int opUserId, String opUsername, String opResultDetail) {
		var log = CouponLogEntity.builder()
				.activity(0, "")
				.coupon(0)
				.customer(0, "")
				.input(couponCode, secretCode)
				.op(OP_VERIFY, opUserId, opUsername, opTime, RESULT_FAILURE, opResultDetail, null)
				.store(company.id, company.name, store.id, store.name)
				.build();

		couponLogService.saveLog(log);

		return new VerifyCouponVo(log, null);
	}

	public RedeemResponseVo redeem(RedeemRequestVo vo) {
		return null;
	}

	// FIXME: 使用前验证优惠券的有效性
	@Transactional
	public List<CouponLogEntity> redeem(int activityId, List<String> couponCodes, int opStoreId, int opUserId,
			String remark) throws NoSuchUserException, NoSuchStoreException, NoSuchCompanyException {
		var now = DateTimeUtils.instantNow();
		var user = userService.queryByIdAndStoreId(opUserId, opStoreId);
		var store = companyService.queryStoreById(opStoreId);
		var company = companyService.queryById(store.companyId);
		var codes = obtainmentRepository.findByCouponCodeIn(couponCodes);
		var logs = codes.stream().map(it -> CouponLogEntity.builder()
				.activity(it.activity.getId(), it.activity.title)
				.coupon(it.getId())
				.customer(it.customer.getId(), it.customer.customerNumber)
				.input(it.couponCode, it.secretCode)
				.op(OP_REDEEM, opUserId, user.username, now, RESULT_SUCCESS, null, remark)
				.store(company.id, company.name, store.id, store.name)
				.build())
				.collect(Collectors.toList());

		codes.forEach(it -> {
			it.status = CouponStatus.USED;
			it.usedTime = now;
		});

		obtainmentRepository.saveAll(codes);
		couponLogService.saveLogs(logs);

		return logs;
	}

}
